// io.c
// Joseph Craig, Rebecca Rush, Steven Andryzcik
// Version 6 - 12/08/2011

#include "io.h"
#include "pqueue.h"

/*********************************** Macros ***********************************/
#define NEXT_REQUEST request_add_count++
extern ppcb cop;

extern pq blocked, ready;

/****************************** Global Variables ******************************/
iocb
	port_cb     = {NULL},
	terminal_cb = {NULL};

pq
	port_queue,
	terminal_queue;

unsigned long request_add_count = 0l;

extern params *param_ptr;

/************************ Utitlity Function Prototypes ************************/
int compare_requests(void *iod1, void *iod2);
int delete_request(void *block);
int find_request(void *block, int argc, void **argv);

/***************************** Utility Functions ******************************/
// compare_requests
// Parameters: Two iods
// Return: True or False
// Functions called: none
// Helper function for pqueue that compares iods to place them in fifo order.
int compare_requests(void *iod1, void *iod2) {
	return (((piod) iod1)->added < ((piod) iod2)->added ? TRUE : FALSE);
}

// delete_request
// Parameters: void pointer
// Return OK or error code from sys_free_mem
// Functions called: sys_free_mem
// Frees the memory associated with an iod block. 
int delete_request(void *block) {
	return sys_free_mem(block);
}

// find_request
// Parameters: void pointer, int number of arguments, array with pcb name
// Return: True or False
// Functions called: strcmp
// Determines if two process names are equal. 
int find_request(void *block, int argc, void **argv) {
	argc = argc;
	
	return (
		strcmp(((piod) block)->mypcb->name, (char *) argv[0]) == 0 ?
		TRUE :
		FALSE
	);
}

/**************************** Function Definitions ****************************/
// empty_io
// Parameters: int pointer to capture what the function failed on
// Return: error code
// Functions called: pq_free, com_close
// Frees the io queues for port and terminal
int empty_io(int *failed_on) {
	int error_code;

	if ((error_code = pq_free(&port_queue)) != OK) {
		*failed_on = PORT_QUEUE;
	} else if ((error_code = pq_free(&terminal_queue)) != OK) {
		*failed_on = TERMINAL_QUEUE;
	} else if ((error_code = com_close()) != OK) {
		*failed_on = COM_PORT;
	} else if ((error_code = trm_close()) != OK) {
		*failed_on = TERMINAL;
	}
	
	return error_code;
}

// intialize_io
// Parameters: int pointer to capture what the function failed on
// Return: error_code
// Functions called: pq_init
// Initializes the io queues for comport and terminal. 
int initialize_io(int *failed_on) {
	int error_code;
	
	if (
		(error_code = pq_init(
			&port_queue,
			compare_requests,
			delete_request,
			find_request
		)) != OK
	) {
		*failed_on = PORT_QUEUE;
	}
	
	else if (
		(error_code = pq_init(
			&terminal_queue,
			compare_requests,
			delete_request,
			find_request
		)) != OK
	) {
		*failed_on = TERMINAL_QUEUE;
	}
	
	else if ((error_code = trm_open(&(terminal_cb.event_flag))) != OK) {
		*failed_on = TERMINAL;
	}
	
	else if ((error_code = com_open(&(port_cb.event_flag), 1200)) != OK) {
		*failed_on = COM_PORT;
	}
	
	return error_code;
}

// io_scheduler
// Parameters: none
// Return: OK or error code
// Functions called: sys_alloc_mem,sizeof, printf, 
// pq_enqueue, pq_find, sys_exit
// Called in sys_call, io_scheduler processes a new I/O request.
int io_scheduler(){
	ppcb temp;
	
	piod myIOD = sys_alloc_mem(sizeof(iod));
	int device_id = param_ptr->device_id;
	
	/*Check which device is being requested*/

	if(device_id == NO_DEV){
		printf("Error: No Device.");
		return NO_DEV;
	}
	/*Create an IOD for the new request*/
	/*Set the PCB in the iod to cop*/
	myIOD->mypcb = cop;
	/*Set the name*/
	strcpy(myIOD->pcbName, cop->name);
	/*Set the buffer address to the parameter* buf_addr*/
	myIOD->buffer_location = param_ptr->buf_addr;
	/*Set the count address to the parameter* count_addr*/
	myIOD->count_buffer = param_ptr->count_addr;
	/*Set the request to the parameter* op_code*/
	myIOD->request_type = param_ptr->op_code;
	
	if(device_id == TERMINAL){
		/*If waiting queue for that device is not empty*/
		/*Insert the new IOD into the waiting queue at the tail*/
		pq_enqueue(&terminal_queue, (void *)myIOD);
		if (terminal_cb.current == NULL) {
			terminal_cb.event_flag = 1;
		}
	}
	else if(device_id == COM_PORT){
		/*If waiting queue for that device is not empty*/
		/*Insert the new IOD into the waiting queue at the tail*/
		pq_enqueue(&port_queue, (void *)myIOD);
		if (port_cb.current == NULL) {
			port_cb.event_flag = 1;
		}
	}
	
	/*Block the process and return*/
	if (
		pq_find(
			(device_id == TERMINAL ? &terminal_queue : &port_queue),
			1,
			(void **) &cop->name,
			(void **) &temp
		) == OK
	) {
		printf("\n\tFATAL ERROR: process %s already blocked...\n\n", cop->name);
		sys_exit();
	}
	pq_enqueue(&blocked, (void *) cop);

	return OK;

}

// prime_request
// Parameters: device id
// Return: void
// Functions called: trm_read, trm_write, trm_clear, trm_gotoxy
// printf, com_read, com_write
// Checks the device id and processes the request. 
void prime_request(int device_id){
	int request;
	if(device_id == TERMINAL){
		request = (terminal_cb.current)->request_type;
		if(request == READ){
			trm_read((terminal_cb.current)->buffer_location, (terminal_cb.current)->count_buffer);
		}
		else if(request == WRITE){
			trm_write((terminal_cb.current)->buffer_location, (terminal_cb.current)->count_buffer);
		}
		else if(request == CLEAR){
			trm_clear();
		}
		else if(request == GOTOXY){
			trm_gotoxy((int)(((terminal_cb.current)->buffer_location)[0]), (int)(((terminal_cb.current)->buffer_location)[1]));
		}
		else{
			printf("Not a valid request");
		}
	}
	else if(device_id == COM_PORT){
		request = (port_cb.current)->request_type;
		if(request == READ){
			com_read((port_cb.current)->buffer_location, (port_cb.current)->count_buffer);
		}
		else if(request == WRITE){
			com_write((port_cb.current)->buffer_location, (port_cb.current)->count_buffer);
		}
		else if(request == CLEAR){
			printf("Clear request is N/A");
		}
		else if(request == GOTOXY){
			printf("GOTOXY request is N/A");
		}
		else{
			printf("Not a valid request");
		}
	}
}